syntax = "proto3";

package kuma.mesh.v1alpha1;

option go_package = "github.com/kumahq/kuma/api/mesh/v1alpha1";

import "api/mesh/options.proto";
import "google/protobuf/wrappers.proto";
import "api/mesh/v1alpha1/selector.proto";
import "validate/validate.proto";
import "kuma-doc/config.proto";

option (doc.config) = {
  type : Policy,
  name : "TrafficRoute",
  file_name : "traffic-route"
};

// TrafficRoute defines routing rules for the traffic in the mesh.
message TrafficRoute {

  option (kuma.mesh.resource).name = "TrafficRouteResource";
  option (kuma.mesh.resource).type = "TrafficRoute";
  option (kuma.mesh.resource).package = "mesh";
  option (kuma.mesh.resource).kds.send_to_zone = true;
  option (kuma.mesh.resource).ws.name = "traffic-route";
  option (kuma.mesh.resource).allow_to_inspect = true;

  // List of selectors to match data plane proxies that are sources of traffic.
  repeated Selector sources = 1
      [ (validate.rules).repeated .min_items = 1, (doc.required) = true ];

  // List of selectors to match services that are destinations of traffic.
  //
  // Notice the difference between sources and destinations.
  // While the source of traffic is always a data plane proxy within a mesh,
  // the destination is a service that could be either within or outside
  // of a mesh.
  repeated Selector destinations = 2
      [ (validate.rules).repeated .min_items = 1, (doc.required) = true ];

  // Split defines a destination with a weight assigned to it.
  message Split {

    // Weight assigned to that destination.
    // Weights are not percentages. For example two destinations with
    // weights the same weight "1" will receive both same amount of the traffic.
    // 0 means that the destination will be ignored.
    google.protobuf.UInt32Value weight = 1 [ (doc.required) = true ];

    // Selector to match individual endpoints that comprise that destination.
    //
    // Notice that an endpoint can be either inside or outside the mesh.
    // In the former case an endpoint corresponds to a data plane proxy,
    // in the latter case an endpoint is an External Service.
    map<string, string> destination = 2 [
      (validate.rules).map = {
        min_pairs : 1,
        keys : {string : {min_len : 1}},
        values : {string : {min_len : 1}}
      },
      (doc.required) = true
    ];
  };

  // LoadBalancer defines the load balancing policy and configuration.
  message LoadBalancer {

    // RoundRobin is a simple policy in which each available upstream host is
    // selected in round robin order.
    message RoundRobin {}

    // LeastRequest uses different algorithms depending on whether hosts have
    // the same or different weights.
    message LeastRequest {
      // The number of random healthy hosts from which the host with the fewest
      // active requests will be chosen. Defaults to 2 so that we perform
      // two-choice selection if the field is not set.
      uint32 choice_count = 1;
    }

    // RingHash implements consistent hashing to upstream hosts.
    message RingHash {
      // The hash function used to hash hosts onto the ketama ring. The value
      // defaults to 'XX_HASH'.
      string hash_function = 1;

      // Minimum hash ring size.
      uint64 min_ring_size = 2;

      // Maximum hash ring size.
      uint64 max_ring_size = 3;
    }

    // Random selects a random available host.
    message Random {}

    // Maglev implements consistent hashing to upstream hosts.
    message Maglev {}

    oneof lb_type {
      RoundRobin round_robin = 1;
      LeastRequest least_request = 2;
      RingHash ring_hash = 3;
      Random random = 4;
      Maglev maglev = 5;
    }
  };

  // Conf defines the destination configuration.
  message Conf {
    // List of destinations with weights assigned to them.
    // When used, "destination" is not allowed.
    repeated Split split = 1 [ (validate.rules).repeated .min_items = 1 ];
    // Load balancer configuration for given "split" or "destination"
    LoadBalancer load_balancer = 2;
    // One destination that the traffic will be redirected to.
    // When used, "split" is not allowed.
    map<string, string> destination = 3;
    // Configuration of HTTP traffic. Traffic is matched one by one with the
    // order defined in the list. If the request does not match any criteria
    // then "split" or "destination" outside of "http" section is executed.
    repeated Http http = 4;
  };

  // Http defines configuration for HTTP traffic.
  message Http {
    // Match defines a series of matching criteria to apply modification and
    // reroute the traffic.
    message Match {
      // StringMatcher matches the string value.
      message StringMatcher {
        oneof matcherType {
          // Prefix matches the string against defined prefix.
          string prefix = 1;
          // Exact checks that strings are equal to each other.
          string exact = 2;
          // Regex checks the string using RE2 syntax.
          // https://github.com/google/re2/wiki/Syntax
          string regex = 3;
        }
      }

      // Method matches method of HTTP request.
      StringMatcher method = 1;
      // Path matches HTTP path.
      StringMatcher path = 2;
      // Headers match HTTP request headers.
      map<string, StringMatcher> headers = 3;
    }

    // Modify defines modifications of matched HTTP messages.
    message Modify {

      // RegexReplace defines a way to match string using regex and build a new
      // one using substitution section.
      message RegexReplace {
        // Pattern of the regex using RE2 syntax.
        // https://github.com/google/re2/wiki/Syntax
        string pattern = 1 [ (doc.required) = true ];
        // Substitution using regex groups. E.g. use \\1 as a first matched
        // group.
        string substitution = 2 [ (doc.required) = true ];
      }

      // Path defines modification of path of the HTTP request.
      message Path {
        oneof type {
          // RewritePrefix rewrites previously matched prefix in match section.
          string rewritePrefix = 1;
          // Regex rewrites prefix using regex with substitution.
          RegexReplace regex = 2;
        }
      }

      // Host defines modification of the HTTP Host header
      message Host {
        oneof type {
          // Value replaces the host header with given value.
          string value = 1;
          // FromPath replaces the host header from path using regex.
          RegexReplace fromPath = 2;
        }
      }

      // Headers defines modification of HTTP headers.
      message Headers {
        // Add defines operation of adding new HTTP header.
        message Add {
          // Name of the header.
          string name = 1 [ (doc.required) = true ];
          // Value of the header.
          string value = 2 [ (doc.required) = true ];
          // If true, it appends the value if there is already a value.
          // Otherwise, value of existing header will be replaced.
          bool append = 3;
        }
        // Remove defines operation of removing an HTTP header.
        message Remove {
          // Name of the header to remove.
          string name = 1 [ (doc.required) = true ];
        }
        // List of add header operations.
        repeated Add add = 1;
        // List of remove header operations.
        repeated Remove remove = 2;
      }

      // Path modifications.
      Path path = 1;
      // Host modifications.
      Host host = 2;
      // Request headers modifications.
      Headers requestHeaders = 3;
      // Response headers modifications.
      Headers responseHeaders = 4;
    }

    // If request matches against defined criteria then "split" or "destination"
    // is executed.
    Match match = 1;
    // Modifications to the traffic matched by the match section.
    Modify modify = 2;
    // List of destinations with weights assigned to them.
    // When used, "destination" is not allowed.
    repeated Split split = 3;
    // One destination that the traffic will be redirected to.
    // When used, "split" is not allowed.
    map<string, string> destination = 4;
  }

  // Configuration for the route.
  Conf conf = 3
      [ (validate.rules).message.required = true, (doc.required) = true ];
}
